 using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Net;
using WMM.Tapioca.Taxonomy;
using WMM.Tapioca.Linkbase;
using WMM.Tapioca.Instance;
using WMM.Tapioca.Utilities;
using System.IO;
using System.Web;
using System.Xml.Linq;

namespace WMM.Tapioca.Taxonomy
{
    /// <summary>
    /// XBRL 2.1 Specification 5.1
    /// The XBRL instance schema defines the abstract elements item and tuple.
    /// </summary>
    public class TaxonomySchema : XbrlDocument
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private InstanceDocument containingXbrlInstance;
        private XmlSchema xmlSchema;
        private XmlSchemaSet xmlSchemaSet;
        private XbrlDocument parentDocument;

        /// <summary>
        /// The parent document where this schema document was discovered.
        /// </summary>
        public XbrlDocument ParentDocument
        {
            get { return parentDocument; }
            set { parentDocument = value; }
        }

		/// <summary>
		/// The root node of the parsed schema document.
		/// </summary>
		internal XElement SchemaRootNode
		{
			get;
			private set;
		}

		/// <summary>
		/// A collection of <see cref="Concept"/> objects representing all elements defined in the schema.
		/// </summary>
        //public List<Concept> Concepts
        //{
        //    get;
        //    private set;
        //}

        public IndexedList<Concept> Concepts
        {
            get;
            private set;
        }

        /// <summary>
        /// The XmlSchema associated with the parsed schema document.
        /// </summary>
        public XmlSchema XmlSchema
        {
            get
            {
                return xmlSchema;
            }
        }

        /// <summary>
        /// The XmlSchemaSet associated with the parsed schema document.
        /// </summary>
        public XmlSchemaSet XmlSchemaSet
        {
            get
            {
                return xmlSchemaSet;
            }
        }


        /// <summary>
        /// Constructor with schema file loaded first.
        /// </summary>
        public TaxonomySchema(XbrlDocument xbrlDocument, XmlUrlResolver resolver)
        {
            parentDocument = null;

            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.DtdProcessing = DtdProcessing.Parse;
                settings.XmlResolver = resolver;

                XmlReader xmlReader = XmlTextReader.Create(xbrlDocument.PathFile, settings);

                xmlSchema = XmlSchema.Read(xmlReader, null);
                xmlSchemaSet = new XmlSchemaSet();

                if (resolver != null)
                {
                    xmlSchemaSet.XmlResolver = resolver;
                }

                xmlSchemaSet.Add(xmlSchema);
                xmlSchemaSet.Compile();
            }
            catch (XmlSchemaException xmlSchemaEx)
            {
                log.Error("Validation error on schema:" + xbrlDocument.FileName, new XbrlException(xmlSchemaEx.Message, xmlSchemaEx));
                Dts.ValidatedSchema = false;
            }
            catch (WebException webEx)
            {
                log.Error("Validation error on schema:" + xbrlDocument.FileName, new XbrlException(webEx.Message, webEx));
                throw new XbrlException(webEx.Message.ToString(), webEx);
            }
            this.FileName = xbrlDocument.FileName;
            this.PathFile = xbrlDocument.PathFile;
            this.ThisXDocument = xbrlDocument.ThisXDocument;
            this.DocType = this.ThisXDocument.Root.Name.LocalName;
            this.targetNamespace = xbrlDocument.ThisXDocument.Root.Name.NamespaceName;
            this.namespaceManager = xbrlDocument.ThisXDocument.CreateNamespaceManager();

            ReadSchemaRootNode();
            ReadConcepts();

            this.Dts = new DTS(this, resolver);
        }


        /// <summary>
        /// The constructor this schema document.
        /// </summary>
        public TaxonomySchema(DTS dtsSchema, string SchemaFilename, XbrlDocument parentDoc, string Base, XmlUrlResolver resolver)
            : base(dtsSchema, SchemaFilename, parentDoc != null ? parentDoc.ThisXDocument.Root.BaseUri : "", Base, resolver)
        {
            if (dtsSchema != null && dtsSchema.Instance != null)
            {
                containingXbrlInstance = dtsSchema.Instance;
            }

            parentDocument = parentDoc;

            try
            {
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.DtdProcessing = DtdProcessing.Parse;
                settings.XmlResolver = resolver;

                XmlReader xmlReader = XmlTextReader.Create(PathFile, settings);
                
                xmlSchema = XmlSchema.Read(xmlReader, null);
                xmlSchemaSet = new XmlSchemaSet();

                if (resolver != null)
                {
                    xmlSchemaSet.XmlResolver = resolver;

                }

                xmlSchemaSet.Add(xmlSchema);
                xmlSchemaSet.Compile();
            }
            catch (XmlSchemaException xmlSchemaEx)
            {              
                log.Debug("Validation error on schema:" + SchemaFilename, new XbrlException(xmlSchemaEx.Message, xmlSchemaEx));
                log.Error("Validation error on schema:" + SchemaFilename + ": " + xmlSchemaEx.Message);
                Dts.ValidatedSchema = false;
                throw new XbrlException(xmlSchemaEx.Message.ToString(), xmlSchemaEx); 
            }
            catch (WebException webEx)
            {
                log.Debug("Validation error on schema:" + SchemaFilename, new XbrlException(webEx.Message, webEx));
                log.Error("Validation error on schema:" + SchemaFilename, new XbrlException(webEx.Message, webEx));
                throw new XbrlException(webEx.Message.ToString(), webEx);
            }

            ReadSchemaRootNode();
            ReadConcepts();
        }


        /// <summary>
        /// Returns a Concept object to a specific name within this taxonomy schema.
        /// </summary>
        /// <param name="name">
        ///            The name for which the element shall be obtained. </param>
        /// <returns> The according Concept object from this taxonomy. If the concept
        ///         is not found in this taxonomy, null is returned. </returns>/// 
                 
        public Concept GetConceptByName(string ns, string name)
        {
            //return this.Concepts.Find(x => x.Name == name);  
            int hashCode = GetNameHashCode(ns, name);
            return this.Concepts.ContainsKeyByName(hashCode) ? this.Concepts.GetItemByName(hashCode) : null;
        }


        public Concept GetConceptByName(string name)
        {
            foreach (Concept CurrentConcept in this.Concepts)
            {
                if (CurrentConcept.Name.Equals(name))
                {
                    return CurrentConcept;
                }
            }

            return null;
        }


        public int GetNameHashCode(string ns, string name)
        {
            int hash = 1;
            hash = hash * 51 + (name != null ? name.GetHashCode() : 0);
            hash = hash * 51 + (ns != null ? ns.GetHashCode() : 0);
            return hash;
        }


        /// <summary>
        /// Returns a Concept object to a specific ID within this taxonomy schema.
        /// </summary>
        /// <param name="id">
        ///            The ID for which the concept shall be obtained. </param>
        /// <returns> The according Concept object from this taxonomy. If the concept
        ///         is not found in this taxonomy, null is returned. </returns>
        public virtual Concept GetConceptByID(string id)
        {
            //return this.Concepts.Find(x => x.Id == id);            
            return this.Concepts.ContainsKey(id.GetHashCode()) ? this.Concepts[id.GetHashCode()] : null;

            //foreach (Concept CurrentConcept in this.Concepts)
            //{
            //    if (CurrentConcept.Id != null)
            //    {
            //        if (CurrentConcept.Id.Equals(id))
            //        {
            //            return CurrentConcept;
            //        }
            //    }
            //}

            //return null;
        }

        /// <summary>
        /// Reads the schema's root node and collects namespace data from the namespace attributes.
        /// </summary>
        private void ReadSchemaRootNode()
        {
            this.Concepts = new IndexedList<Concept>();
            //this.Concepts = new List<Concept>();
            this.SchemaRootNode = ThisXDocument.Root;

			if (this.SchemaRootNode == null)
            {
                StringBuilder MessageBuilder = new StringBuilder();
                string StringFormat = AssemblyResources.GetName("SchemaFileCandidateDoesNotContainSchemaRootNode");
                MessageBuilder.AppendFormat(StringFormat, this.PathFile);
                log.Error("Validation error on schema:", new XbrlException(MessageBuilder.ToString()));
                throw new XbrlException(MessageBuilder.ToString());
            }

            this.TargetNamespace = (string)this.SchemaRootNode.Attribute("targetNamespace") ?? string.Empty;
        }


        private void ReadConcepts()
        {
            foreach (DictionaryEntry CurrentEntry in xmlSchemaSet.GlobalElements)
            {
                XmlSchemaElement CurrentElement = CurrentEntry.Value as XmlSchemaElement;

                Concept concept = new Concept(this, CurrentElement);
                this.Concepts.AddById(concept);
                this.Concepts.AddByName(concept);
            }
        }

 
        internal XmlSchemaType GetXmlSchemaType(XmlQualifiedName ItemTypeValue)
        {
            foreach (DictionaryEntry CurrentEntry in xmlSchemaSet.GlobalTypes)
            {
                XmlSchemaType CurrentType = CurrentEntry.Value as XmlSchemaType;

                if (CurrentType.QualifiedName.Equals(ItemTypeValue))
                {
                    return CurrentType;
                }
            }

            return null;
        }


        internal Concept LocateConcept(Locator locator)
        {
            foreach (Concept CurrentConcept in this.Concepts)
            {
                return this.Concepts.ContainsKey(HttpUtility.UrlDecode(locator.HrefResourceId).GetHashCode()) ? this.Concepts[HttpUtility.UrlDecode(locator.HrefResourceId).GetHashCode()] : null;

                //if (!string.IsNullOrEmpty(CurrentConcept.Id))
                //{
                //    if (CurrentConcept.Id.Equals(HttpUtility.UrlDecode(locator.HrefResourceId)))
                //    {
                //        return CurrentConcept;
                //    }
                //}
            }

            return null;
        }
 
    }
}
